package com.uyayo.framework.captcha;

import com.uyayo.framework.captcha.model.ClickPoint;
import com.uyayo.framework.captcha.model.SliderCaptcha;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.FileImageOutputStream;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.text.NumberFormat;
import java.util.*;

/**
 * 滑动验证引擎
 * @author hfj
 * @date 2018/10/20
 */
public class SliderGraphicsEngine {
    private Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * 图片宽度
     */
    private int bgImageWidth = 330;

    /**
     * 图片长度
     */
    private int bgImageHeight = 160;

    /**
     * 背景图片目录
     */
    private String bgImageDir;

    /**
     * 滑块图片目录
     */
    private String sliderImageDir;

    /**
     * 滑块图片宽度
     */
    private int sliderImageWidth = 4;
    /**
     * 滑块图片高度
     */
    private int sliderImageHeight = 3;


    public int getBgImageWidth() {
        return bgImageWidth;
    }

    public void setBgImageWidth(int bgImageWidth) {
        this.bgImageWidth = bgImageWidth;
    }

    public int getBgImageHeight() {
        return bgImageHeight;
    }

    public void setBgImageHeight(int bgImageHeight) {
        this.bgImageHeight = bgImageHeight;
    }

    public String getBgImageDir() {
        return bgImageDir;
    }

    public void setBgImageDir(String bgImageDir) {
        this.bgImageDir = bgImageDir;
    }

    public String getSliderImageDir() {
        return sliderImageDir;
    }

    public void setSliderImageDir(String sliderImageDir) {
        this.sliderImageDir = sliderImageDir;
    }

    public int getSliderImageWidth() {
        return sliderImageWidth;
    }

    public void setSliderImageWidth(int sliderImageWidth) {
        this.sliderImageWidth = sliderImageWidth;
    }

    public int getSliderImageHeight() {
        return sliderImageHeight;
    }

    public void setSliderImageHeight(int sliderImageHeight) {
        this.sliderImageHeight = sliderImageHeight;
    }

    public SliderGraphicsEngine() {
    }

    public SliderGraphicsEngine(int bgImageWidth,
                                int bgImageHeight,
                                String bgImageDir,
                                String sliderImageDir,
                                int sliderImageWidth,
                                int sliderImageHeight) {
        this.bgImageWidth = bgImageWidth;
        this.bgImageHeight = bgImageHeight;
        this.bgImageDir = bgImageDir;
        this.sliderImageDir =sliderImageDir;
        this.sliderImageWidth = sliderImageWidth;
        this.sliderImageHeight = sliderImageHeight;
    }

    /**
     * 随机获取一个文件
     * @param fileDir  文件目录
     * @return 随机文件
     */
    private File getRandomFile(String fileDir) throws IOException {
        File dir = new File(fileDir);
        if(dir.exists() && dir.isDirectory()) {
            File[] files = dir.listFiles();
            if(files == null) {
                logger.error("找不到背景图片集合:{}",fileDir);
                throw new FileNotFoundException("没有背景图片");
            } else {
                int len = files.length;
                if(len <= 0) {
                    logger.error("找不到背景图片:{}",fileDir);
                    throw new FileNotFoundException("没有背景图片");
                }
                int random = new Random().nextInt(len);
                return files[random];
            }

        } else {
            logger.error("找不到背景图片目录:{}",fileDir);
            throw new FileNotFoundException("找不到背景图片目录");
        }
    }


    /**
     * 绘制字点击验证码
     * @return SliderCaptcha
     * @throws IOException IO异常
     */
    public SliderCaptcha genSlideVerifyCaptcha() throws Exception {

        // 源背景图文件流
        File oriFile = getRandomFile(bgImageDir);
        InputStream oriIs = new FileInputStream(oriFile);

        //获取抠图模板图的宽和高信息
        BufferedImage imageTemplate = ImageIO.read(getRandomFile(sliderImageDir));
        sliderImageWidth = imageTemplate.getWidth();
        sliderImageHeight = imageTemplate.getHeight();

        //随机生成源背景图中抠缺口图坐标x,y
        ClickPoint clickPoint = generateCutoutCoordinates();
        int x = clickPoint.getX();
        int y = clickPoint.getY();
        //抠图模板
        BufferedImage sliderImageBufferedImage = new BufferedImage(sliderImageWidth, sliderImageHeight, imageTemplate.getType());
        Graphics2D graphics = sliderImageBufferedImage.createGraphics();
        graphics.setBackground(Color.white);

        //从源背景图片中在随机位置x,y处获取与抠图模板大小一致的区域图
        BufferedImage sliderImageNoDeal = getSliderArea(x,y, sliderImageWidth, sliderImageHeight, oriIs, "jpg");

        //用源背景图片中对应区域的像素信息渲染抠图模板，形成可拖动的小拼块
        sliderImageBufferedImage = dealCutPictureByTemplate(sliderImageNoDeal, imageTemplate, sliderImageBufferedImage);

        //设置拼块的“抗锯齿”的属性
        int bold = 5;
        graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
        graphics.drawImage(sliderImageBufferedImage, 0, 0, null);
        graphics.dispose();

        //利用ImageIO类提供的write方法，将bi以png图片的数据模式写入流。
        //新建流。
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        ImageIO.write(sliderImageBufferedImage, "png", os);

        //对源背景图中被扣去的部分生成透明遮罩
        BufferedImage oriImage = ImageIO.read(oriFile);
        byte[] oriCopyImages = dealOriPictureByTemplate(oriImage, imageTemplate, x, y);
        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(oriCopyImages);
        BufferedImage image = ImageIO.read(byteArrayInputStream);

        SliderCaptcha captcha = new SliderCaptcha();
        captcha.setSliderImage(sliderImageBufferedImage);
        captcha.setImage(image);
        captcha.setClickPoint(new ClickPoint(x,y));
        return captcha;

    }

    /**
     * 抠图后原图生成
     *
     * @param oriImage
     * @param templateImage
     * @param x
     * @param y
     * @return
     * @throws Exception
     */
    private  byte[] dealOriPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x,
                                             int y) throws Exception {
        // 源文件备份图像矩阵 支持alpha通道的rgb图像
        BufferedImage oriCopyImage = new BufferedImage(oriImage.getWidth(), oriImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
        // 源文件图像矩阵
        int[][] oriImageData = getData(oriImage);
        // 模板图像矩阵
        int[][] templateImageData = getData(templateImage);

        //copy 源图做不透明处理
        int origWidth = oriImageData.length;
        int origHeight = oriImageData[0].length;
        for (int i = 0; i < origWidth; i++) {
            for (int j = 0; j < origHeight; j++) {
                int rgb = oriImage.getRGB(i, j);
                int r = (0xff & rgb);
                int g = (0xff & (rgb >> 8));
                int b = (0xff & (rgb >> 16));
                //无透明处理
                rgb = r + (g << 8) + (b << 16) + (255 << 24);
                oriCopyImage.setRGB(i, j, rgb);
            }
        }

        for (int i = 0; i < templateImageData.length; i++) {
            for (int j = 0; j < templateImageData[0].length - 5; j++) {
                int rgb = templateImage.getRGB(i, j);
                //对源文件备份图像(x+i,y+j)坐标点进行透明处理
                if (rgb != 16777215 && rgb <= 0 && x + i<origWidth && y + j<origHeight) {
                    int rgb_ori = oriCopyImage.getRGB(x + i, y + j);
                    int r = (0xff & rgb_ori);
                    int g = (0xff & (rgb_ori >> 8));
                    int b = (0xff & (rgb_ori >> 16));
                    rgb_ori = r + (g << 8) + (b << 16) + (150 << 24);
                    oriCopyImage.setRGB(x + i, y + j, rgb_ori);
                } else {
                    //do nothing
                }
            }
        }
        ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
        ImageIO.write(oriCopyImage, "png", os);//利用ImageIO类提供的write方法，将bi以png图片的数据模式写入流。
        byte b[] = os.toByteArray();//从流中获取数据数组。
        return b;
    }

    /**
     * 根据模板图片抠图
     *
     * @param oriImage
     * @param templateImage
     * @return
     */
    private  BufferedImage dealCutPictureByTemplate(BufferedImage oriImage, BufferedImage templateImage,
                                                    BufferedImage sliderImage) throws Exception {
        // 源文件图像矩阵
        int[][] oriImageData = getData(oriImage);
        int originalWidth = oriImage.getWidth();
        int originalHeight = oriImage.getHeight();
        // 模板图像矩阵
        int[][] templateImageData = getData(templateImage);
        // 模板图像宽度

        for (int i = 0; i < templateImageData.length; i++) {
            // 模板图片高度
            for (int j = 0; j < templateImageData[0].length; j++) {
                // 如果模板图像当前像素点不是白色 copy源文件信息到目标图片中
                int rgb = templateImageData[i][j];
                if (rgb != 16777215 && rgb <= 0 &&i<originalWidth&&j<originalHeight) {
                    sliderImage.setRGB(i, j, oriImageData[i][j]);
                }
            }
        }
        return sliderImage;
    }

    /**
     * 获取目标区域
     *
     * @param x            随机切图坐标x轴位置
     * @param y            随机切图坐标y轴位置
     * @param sliderWidth  切图后目标宽度
     * @param sliderHeight 切图后目标高度
     * @param ois          源文件输入流
     * @return
     * @throws Exception
     */
    private  BufferedImage getSliderArea(int x, int y, int sliderWidth, int sliderHeight, InputStream ois,
                                         String filetype) throws Exception {
        Iterator<ImageReader> imageReaderList = ImageIO.getImageReadersByFormatName(filetype);
        ImageReader imageReader = imageReaderList.next();
        // 获取图片流
        ImageInputStream iis = ImageIO.createImageInputStream(ois);
        // 输入源中的图像将只按顺序读取
        imageReader.setInput(iis, true);

        ImageReadParam param = imageReader.getDefaultReadParam();
        Rectangle rec = new Rectangle(x, y, sliderWidth, sliderHeight);
        param.setSourceRegion(rec);
        BufferedImage sliderImage = imageReader.read(0, param);
        return sliderImage;
    }

    /**
     * 生成图像矩阵
     *
     * @param
     * @return
     * @throws Exception
     */
    private  int[][] getData(BufferedImage bimg) throws Exception {
        int[][] data = new int[bimg.getWidth()][bimg.getHeight()];
        for (int i = 0; i < bimg.getWidth(); i++) {
            for (int j = 0; j < bimg.getHeight(); j++) {
                data[i][j] = bimg.getRGB(i, j);
            }
        }
        return data;
    }

    /**
     * 随机生成抠图坐标
     */
    private ClickPoint generateCutoutCoordinates() {
        Random random = new Random();
        int widthDifference = bgImageWidth - sliderImageWidth;
        int heightDifference = bgImageHeight - sliderImageHeight;
        ClickPoint clickPoint = new ClickPoint();
        if (widthDifference <= 0) {
            clickPoint.setX(5);
        } else {
            clickPoint.setX(random.nextInt(bgImageWidth - sliderImageWidth) + 5);
        }

        if (heightDifference <= 0) {
            clickPoint.setY(5);
        } else {
            clickPoint.setY(random.nextInt(bgImageHeight - sliderImageHeight) + 5);
        }

        NumberFormat numberFormat = NumberFormat.getInstance();
        numberFormat.setMaximumFractionDigits(2);
        return clickPoint;
        //xPercent = Float.parseFloat(numberFormat.format((float) X / (float) bgImgDefaultWidth));
        //yPercent = Float.parseFloat(numberFormat.format((float) Y / (float) bgImgDefaultHeight));
    }

    private  boolean isEmpty(String templateFiletype) {
        return templateFiletype == null || templateFiletype.equals("");
    }


    public static void main(String args[]) throws Exception {
        SliderGraphicsEngine graphicsEngine = new SliderGraphicsEngine(
                330,
                160,
                "G:\\develop\\项目案例\\taurus-captcha-master\\captcha-core\\src\\main\\resources\\bgImage",
                "G:\\develop\\项目案例\\taurus-captcha-master\\captcha-core\\src\\main\\resources\\bgImage",
                33,
                33);
        SliderCaptcha captcha = graphicsEngine.genSlideVerifyCaptcha();
        String pathName = "C:/text.jpg";
        File file = new File(pathName);
        ImageOutputStream imageOutputStream = new FileImageOutputStream(file);
        ImageIO.write(captcha.getImage(), "jpg", file);
    }

}
